package cn.org.donald.sensitive.factory;

import cn.org.donald.sensitive.model.FactoryEnum;
import cn.org.donald.sensitive.util.StringUtil;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Donald
 * @since 2023-11-10 16:27:08
 **/
public class WordFindSensitive extends IFindSensitive {

    public WordFindSensitive(Path filePath, FactoryEnum factoryEnum) {
        super(filePath, factoryEnum);
    }

    @Override
    public List<String> getSensitiveString(List<String> ruleRegexList) {
        List<String> list = new ArrayList<>();
        try (InputStream inputStream = Files.newInputStream(super.getFilePath())) {
            if (FactoryEnum.DOC.equals(super.getFactoryEnum())) {
                findDoc(inputStream, ruleRegexList, list);
            } else {
                findDocx(inputStream, ruleRegexList, list);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return list;
    }

    @Override
    public void replace(List<String> ruleRegexList, Path destPath) {
        try {
            Files.deleteIfExists(destPath);
            Files.createDirectories(destPath.getParent());
            Files.createFile(destPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String regex = String.join("|", ruleRegexList);
        try (InputStream inputStream = Files.newInputStream(super.getFilePath())) {
            if (FactoryEnum.DOCX.equals(super.getFactoryEnum())) {
                replaceDocx(inputStream, destPath, regex);
            } else {
                replaceDoc(inputStream, destPath, regex);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void findDoc(InputStream inputStream, List<String> ruleRegexList, List<String> result) {
        try (HWPFDocument hwpfDocument = new HWPFDocument(inputStream)) {
            Range range = hwpfDocument.getRange();
            int numParagraphs = range.numParagraphs();
            for (int i = 0; i < numParagraphs; i++) {
                String text = range.getParagraph(i).text();
                result.addAll(super.findSensitiveInString(ruleRegexList, text).collect(Collectors.toList()));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void findDocx(InputStream inputStream, List<String> ruleRegexList, List<String> result) {
        try (XWPFDocument xwpfDocument = new XWPFDocument(inputStream)) {
            xwpfDocument.getParagraphsIterator().forEachRemaining(paragraph -> {
                String text = paragraph.getParagraphText();
                result.addAll(super.findSensitiveInString(ruleRegexList, text).collect(Collectors.toList()));
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void replaceDocx(InputStream inputStream, Path destPath, String regex) {
        try (XWPFDocument xwpfDocument = new XWPFDocument(inputStream)) {
            List<XWPFParagraph> paragraphs = xwpfDocument.getParagraphs();
            for (XWPFParagraph paragraph : paragraphs) {
                List<XWPFRun> runs = paragraph.getRuns();
                String paragraphText = paragraph.getParagraphText();
                String replacement = StringUtil.replaceTargetToStar(paragraphText, regex);
                if (!paragraphText.equals(replacement)) {
                    XWPFRun xwpfRun = runs.get(0);
                    xwpfRun.setText(replacement, 0);
                    for (int i = 1; i < runs.size(); i++) {
                        paragraph.removeRun(1);
                    }
                }
            }
            try (OutputStream outputStream = Files.newOutputStream(destPath, StandardOpenOption.WRITE)) {
                xwpfDocument.write(outputStream);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void replaceDoc(InputStream inputStream, Path destPath, String regex) {
        try (HWPFDocument hwpfDocument = new HWPFDocument(inputStream)) {
            Range range = hwpfDocument.getRange();
            int numParagraphs = range.numParagraphs();
            for (int i = 0; i < numParagraphs; i++) {
                Paragraph paragraph = range.getParagraph(i);
                String text = paragraph.text();
                String replacement = StringUtil.replaceTargetToStar(text, regex);
                if (!text.equals(replacement)) {
                    int numCharacterRuns = paragraph.numCharacterRuns();
                    CharacterRun characterRun = paragraph.getCharacterRun(0);
                    characterRun.replaceText(replacement, false);
                    for (int j = 1; j < numCharacterRuns; j++) {
                        paragraph.getCharacterRun(j).delete();
                    }
                }
            }
            try (OutputStream outputStream = Files.newOutputStream(destPath, StandardOpenOption.WRITE)) {
                hwpfDocument.write(outputStream);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
